#include <assert.h>
#include <vector>
#include <fstream>
#include <sstream>
#include <regex>
#include <stack>

#include "animation_player.h"

using namespace std;
using namespace std::tr1;

namespace
{
bool ReadFile (const char* pFilename, string& content)
{
   ifstream fileIn (pFilename);
   bool ok = (!fileIn.fail ());

   if (ok)
   {
      // Do not remove the extra set of parentheses from the first argument!
      content.assign ((istreambuf_iterator<char> (fileIn)), istreambuf_iterator<char> ());
   }

   fileIn.close ();

   return ok;
}
} // end anonymous namespace

namespace tl
{
void animation_player::RestoreDatabase (void)
{
   m_xformToCode.clear ();

   for (vector<mgrec*>::iterator it = m_xforms.begin (); it != m_xforms.end (); ++it)
   {
      mgrec* xform = *it;
      if (xform)
      {
         mgDelete (xform);
         xform = NULL;
      }
   }

   m_xforms.clear ();
   vector<mgrec*> ().swap (m_xforms);
}

void animation_player::Reset (void)
{
   m_animationPath.clear ();

   if (m_timerHandle)
   {
      mgUnregisterTimer (m_timerHandle);
      m_timerHandle = NULL;
   }

   m_frame = frame_number ();
   m_frameTime = 0.0;
   m_timer.Reset ();
   m_startFrame = frame_number ();

   RestoreDatabase ();

   m_values.clear ();
   vector<double> ().swap (m_values);
}

bool animation_player::LoadBVHFile (const char* pFilename)
{
   string strBVH;
   if (!ReadFile (pFilename, strBVH))
      return false;

   Reset ();
   m_animationPath = pFilename;

   cmatch m;
   const regex jointExp (
      "(?:ROOT|JOINT)\\s+([\\w]+)(?:\\:\\d)?"
      "\\s+\\{"
      "\\s+OFFSET\\s+([-\\+.\\w\\d]+)\\s+([-\\+.\\w\\d]+)\\s+([-\\+.\\w\\d]+)"
      "\\s+CHANNELS\\s+(\\d)\\s*");
   const regex posExp ("(Xposition|Yposition|Zposition)\\s*");
   const regex rotExp ("(Xrotation|Yrotation|Zrotation)\\s*");
   const regex scaleExp ("(Xscale|Yscale|Zscale)\\s*");
   const regex endExp (
      "(?:\\}\\s*)|"
      "(?:End Site"
      "\\s+\\{"
      "\\s+OFFSET\\s+(?:[-\\+.\\w\\d]+)\\s+(?:[-\\+.\\w\\d]+)\\s+(?:[-\\+.\\w\\d]+)"
      "\\s+\\}\\s*)");
   const char* pSubstring = strBVH.c_str ();
   stack<mgcoord3d> offsetStack;
   offsetStack.push (mgCoord3dZero ());

   while (regex_search (pSubstring, m, jointExp))
   {
      pSubstring += m.position () + m.length ();

      mgrec* rec = mgGetRecByName (m_db, m[1].str ().c_str ());

      mgcoord3d offset;
      offset.x = atof (m[2].str ().c_str ());
      offset.y = atof (m[3].str ().c_str ());
      offset.z = atof (m[4].str ().c_str ());

      offsetStack.push (mgCoord3dAdd (&offsetStack.top (), &offset));

      int n = atoi (m[5].str ().c_str ());
      for (int i = 0; i < n; ++i)
      {
         if (regex_search (pSubstring, m, posExp) && m.position () == 0)
         {
            pSubstring += m.position () + m.length ();

            if (rec)
            {
               mgrec* xform = mgNewRec (fltXmTranslate);
               m_xforms.push_back (xform);

               mgSetCoord3d (xform, fltXmTranslateDelta, 0.0, 0.0, 0.0);

               char axis = m[1].str ()[0];
               switch (axis)
               {
               case 'X':
                  mgSetCoord3d (xform, fltXmTranslateFrom, 1.0, 0.0, 0.0);
                  break;
               case 'Y':
                  mgSetCoord3d (xform, fltXmTranslateFrom, 0.0, 1.0, 0.0);
                  break;
               case 'Z':
                  mgSetCoord3d (xform, fltXmTranslateFrom, 0.0, 0.0, 1.0);
                  break;
               }

               mgAppend (rec, xform);
            }
            else
               m_xforms.push_back (NULL);
         }
         else if (regex_search (pSubstring, m, rotExp) && m.position () == 0)
         {
            pSubstring += m.position () + m.length ();

            if (rec)
            {
               mgrec* xform = mgNewRec (fltXmRotate);
               m_xforms.push_back (xform);

               const mgcoord3d& topOffset = offsetStack.top ();
               mgSetCoord3d (xform, fltXmRotateCenter, topOffset.x, topOffset.y, topOffset.z);
               mgSetAttList (xform, fltXmRotateAngle, 0.0f, NULL);

               char axis = m[1].str ()[0];
               switch (axis)
               {
               case 'X':
                  mgSetVector (xform, fltXmRotateAxis, 1.0f, 0.0f, 0.0f);
                  break;
               case 'Y':
                  mgSetVector (xform, fltXmRotateAxis, 0.0f, 1.0f, 0.0f);
                  break;
               case 'Z':
                  mgSetVector (xform, fltXmRotateAxis, 0.0f, 0.0f, 1.0f);
                  break;
               }

               mgAppend (rec, xform);
            }
            else
               m_xforms.push_back (NULL);
         }
         else if (regex_search (pSubstring, m, scaleExp) && m.position () == 0)
         {
            pSubstring += m.position () + m.length ();

            if (rec)
            {
               mgrec* xform = mgNewRec (fltXmScale);
               m_xforms.push_back (xform);

               const mgcoord3d& topOffset = offsetStack.top ();
               mgSetCoord3d (xform, fltXmScaleCenter, topOffset.x, topOffset.y, topOffset.z);
               mgSetAttList (xform, fltXmScaleX, 1.0f, fltXmScaleY, 1.0f, fltXmScaleZ, 1.0f, NULL);

               char axis = m[1].str ()[0];
               switch (axis)
               {
               case 'X':
                  m_xformToCode[xform] = fltXmScaleX;
                  break;
               case 'Y':
                  m_xformToCode[xform] = fltXmScaleY;
                  break;
               case 'Z':
                  m_xformToCode[xform] = fltXmScaleZ;
                  break;
               }

               mgAppend (rec, xform);
            }
            else
               m_xforms.push_back (NULL);
         }
      }

      while (regex_search (pSubstring, m, endExp) && m.position () == 0)
      {
         pSubstring += m.position () + m.length ();

         if (pSubstring[0] == '}')
            offsetStack.pop ();
      }
   }

   const regex motionExp (
      "MOTION"
      "\\s+Frames:\\s+(\\d+)"
      "\\s+Frame Time:\\s+([\\+.\\w\\d]+)");

   regex_search (pSubstring, m, motionExp);
   pSubstring += m.position () + m.length ();

   const int frameCount = atoi (m[1].str ().c_str ());

   m_frame.SetFrameCount (frameCount);
   m_frameTime = atof (m[2].str ().c_str ());

   const size_t valuesSize = m_xforms.size () * frameCount;
   m_values.reserve (valuesSize);

   istringstream ss (pSubstring);
   // Do not remove the extra set of parentheses from the first argument!
   m_values.assign ((istream_iterator<double> (ss)), istream_iterator<double> ());

   return (m_values.size () == valuesSize);
}

void animation_player::UpdateXforms (void)
{
   const size_t xformCount = m_xforms.size ();

   for (size_t i = 0; i < xformCount; ++i)
   {
      mgrec* const xform = m_xforms[i];
      if (!xform)
         continue;

      const double value = m_values[xformCount * m_frame.Get () + i];

      switch (mgGetXformType (xform))
      {
      case MXLL_TRANSLATE:
         {
            double x, y, z;
            mgGetCoord3d (xform, fltXmTranslateFrom, &x, &y, &z);

            if (x != 0.0)
               mgSetCoord3d (xform, fltXmTranslateDelta, value, 0.0, 0.0);
            else if (y != 0.0)
               mgSetCoord3d (xform, fltXmTranslateDelta, 0.0, value, 0.0);
            else if (z != 0.0)
               mgSetCoord3d (xform, fltXmTranslateDelta, 0.0, 0.0, value);
         }
         break;
      case MXLL_ROTPT:
         mgSetAttList (xform, fltXmRotateAngle, static_cast<float>(value), NULL);
         break;
      case MXLL_SCALE:
         mgSetAttList (xform, m_xformToCode[xform], static_cast<float>(value), NULL);
         break;
      }
   }

   mgRefreshGraphicsView (m_db);
}

mgbool animation_player::UpdateFrame (mgtimer timerId, void* userData)
{
   animation_player* pPlayer = static_cast<animation_player*>(userData);
   if (!pPlayer)
      return MG_FALSE;

   if (!pPlayer->IsPlaying ())
      return MG_TRUE;

   const size_t xformCount = pPlayer->m_xforms.size ();
   const int frameDelta = static_cast<int>(pPlayer->m_timer.GetTime () / pPlayer->m_frameTime);

   pPlayer->m_frame = pPlayer->m_startFrame;
   pPlayer->m_frame.ChangeBy (frameDelta);

   pPlayer->UpdateXforms ();

   if (pPlayer->m_spPostUpdateFrameFunctor)
      (*pPlayer->m_spPostUpdateFrameFunctor) (pPlayer);

   return MG_TRUE;
}

void animation_player::Play (void)
{
   if (!m_timerHandle)
   {
      if (m_frameTime > 0.0)
      {
         unsigned int timeout = static_cast<unsigned int>(m_frameTime * 1000.0);
         m_timerHandle = mgRegisterTimer (NULL, timeout, UpdateFrame, this);
      }
   }

   if (!m_timer.IsStarted ())
   {
      m_timer.Reset ();
      m_startFrame = m_frame;
      m_timer.Start ();
   }
}
} // end namespace tl